package org.dicr.traffic.source;

import java.util.*;

import org.slf4j.*;

/**
 * Abstract Traffic Source
 * 
 * @author <A href='http://dicr.org'>Igor A Tarasov</A>
 * @version 060703
 */
public abstract class AbstractTrafficSource implements TrafficSource {

	/** Logger */
	private static final Logger log = LoggerFactory.getLogger(AbstractTrafficSource.class);

	/** Listeners */
	private final Set<TrafficListener> listeners = new HashSet<>();

	/** Count of fired events */
	private long eventsCounter = 0;

	/** Elements count */
	private long producedElementsCount = 0;

	/** Allow firing empty event and traffic */
	private boolean skipEmpty = true;

	/** Constructor */
	protected AbstractTrafficSource() {
		super();
	}

	/**
	 * Set listeners
	 * 
	 * @param aListeners
	 *            listeners to set
	 */
	public void setTrafficListeners(final Set<TrafficListener> aListeners) {
		if (aListeners == null) throw new IllegalArgumentException("null listeners");
		synchronized (this.listeners) {
			this.listeners.clear();
			this.listeners.addAll(aListeners);
		}
	}

	/**
	 * Add traffic listener
	 * 
	 * @param listener
	 *            listener to add
	 */
	@Override
	public void registerListener(final TrafficListener listener) {
		if (listener == null) throw new IllegalArgumentException("null listener");
		synchronized (this.listeners) {
			this.listeners.add(listener);
		}
	}

	/**
	 * Remove traffic listener
	 * 
	 * @param listener
	 *            listener to remove
	 */
	@Override
	public final void unregisterListener(final TrafficListener listener) {
		if (listener == null) throw new IllegalArgumentException("null listener");
		synchronized (this.listeners) {
			this.listeners.remove(listener);
		}
	}

	/**
	 * Return traffic listeners
	 * 
	 * @return set of listeners
	 */
	public Set<TrafficListener> getTrafficListeners() {
		synchronized (this.listeners) {
			return new HashSet<>(this.listeners);
		}
	}

	/**
	 * Return firing empty traffic events is allowed.
	 * 
	 * @return true to allow fire empty traffic event
	 */
	public boolean isSkipEmpty() {
		return this.skipEmpty;
	}

	/**
	 * Set allow empty fire.
	 * 
	 * @param skip
	 *            true to skip fire empty (no traffic) events.
	 */
	public void setSkipEmpty(final boolean skip) {
		synchronized (this) {
			this.skipEmpty = skip;
		}
		AbstractTrafficSource.log.info("configured skip empty traffic events to " + skip);
	}

	/**
	 * Return events counter.
	 * 
	 * @return count of traffic events fired by this traffic source
	 */
	public final long getEventsCounter() {
		return this.eventsCounter;
	}

	/**
	 * Increase events counter.
	 */
	private final void incEventCounter() {
		synchronized (this.listeners) {
			this.eventsCounter++;
			if (this.eventsCounter < 0) this.eventsCounter = 0;
		}
	}

	/**
	 * Return produced elements count.
	 * 
	 * @return count of traffic elements fired by this source
	 */
	public final long getProducedElementsCount() {
		return this.producedElementsCount;
	}

	/**
	 * Increase elements counter
	 */
	private final void incProducedElementsCounter(final long increase) {
		if (increase < 0) throw new IllegalArgumentException("increase: " + increase);
		synchronized (this.listeners) {
			this.producedElementsCount += increase;
			if (this.producedElementsCount < 0) this.producedElementsCount = 0;
		}
	}

	/**
	 * Reset counters. Reset elements and events counters.
	 */
	public void resetCounters() {
		synchronized (this.listeners) {
			this.eventsCounter = 0;
			this.producedElementsCount = 0;
		}
	}

	/**
	 * Fire event to listeners. Fire event in separate thread. If traffic is
	 * empty collection and empty firing not
	 * allowed, then no effect will occur.
	 * 
	 * @param traffic
	 *            traffic to fire
	 */
	protected final void fireTraffic(final Collection<TrafficElement> traffic) {
		if (traffic == null) throw new IllegalArgumentException("null traffic");
		synchronized (this.listeners) {
			if (this.listeners.isEmpty()) return;
			if (traffic.isEmpty() && this.isSkipEmpty()) return;
			new TrafficFirer(new TrafficEvent(this, traffic), this.listeners).start();
			this.incEventCounter();
			this.incProducedElementsCounter(traffic.size());
		}
	}

	/**
	 * Thread that fire traffic to listener
	 */
	protected final static class TrafficFirer extends Thread {

		/** Event to fire */
		private TrafficEvent event = null;

		/** Listeners to fire event to */
		private Collection<TrafficListener> theListeners = null;

		/**
		 * Constructor.
		 * 
		 * @param e
		 *            event to fire
		 * @param aListeners
		 *            listeners to fire
		 */
		public TrafficFirer(final TrafficEvent e, final Collection<TrafficListener> aListeners) {
			super("TrafficSource event firer");
			if (e == null) throw new IllegalArgumentException("null event to fire");
			if (aListeners == null) throw new IllegalArgumentException("null listeners");
			this.event = e;
			this.theListeners = new ArrayList<>(aListeners);
			this.setDaemon(false);
		}

		/**
		 * Fire traffic to listeners
		 * 
		 * @see java.lang.Thread#run()
		 */
		@SuppressWarnings("synthetic-access")
		@Override
		public final void run() {
			if (this.theListeners.size() < 1) return;
			AbstractTrafficSource.log.trace("firing " + this.event.getTraffic().size() + " traffic elements to "
					+ this.theListeners.size() + " listeners");
			for (final TrafficListener l : this.theListeners) {
				try {
					l.processTrafficEvent(this.event);
				} catch (final Exception ex) {
					AbstractTrafficSource.log.error("traffic listener error", ex);
				}
			}
		}
	}
}
